﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FishServe.Core.DesignByContract
{
	using System.Configuration;

	#region App settings helper

	public enum Required { Yes, No }

	public enum GuardedParse { Yes, No }

	/// <summary>
	/// Singleton configuration helper for ConfigurationManager.AppSettings
	/// </summary>
	public static class AppSettingsHelper
	{

		/// <summary>
		/// TODO: Strengthen type return - have to figure out the best way..
		/// </summary>
		/// <param name="?"></param>
		/// <returns></returns>
		private delegate object ValueParser(string pKey, string pValue, GuardedParse pGuarded);
		private static readonly Dictionary<Type, ValueParser> mParserDelegates = new Dictionary<Type, ValueParser>();
		private static readonly GuardedParse mGuidedParseDefault = GuardedParse.No;

		/// <summary>
		/// Sets up delegates for value parsing; there must be a more adaptive manner of
		/// doing this.....
		/// </summary>
		static AppSettingsHelper()
		{
			mParserDelegates[typeof(uint)] = new ValueParser(UnsignedIntegerParser);
			mParserDelegates[typeof(int)] = new ValueParser(IntegerParser);
			mParserDelegates[typeof(bool)] = new ValueParser(BooleanParser);
			mParserDelegates[typeof(string)] = new ValueParser(StringParser);
			// TODO: Dodgy - but set the default for debug efforts to aid, well, debugging
#if DEBUG
			mGuidedParseDefault = GuardedParse.Yes;
#endif
		}

		private static string SanityCheck(string pKey, Required pMandatory)
		{
			return SanityCheck(pKey, pMandatory, null);
		}

		private static string SanityCheck(string pKey, Required pMandatory, string pDefault)
		{
			DBC.Assert(pKey != null, "Null configuration key used!");
			string val = ConfigurationManager.AppSettings[pKey];
			DBC.Assert(val != null || pMandatory == Required.No || pDefault != null,
						String.Format("Configuration value required but value absent ({0})", pKey));
			return val ?? pDefault;
		}

		private static ValueParser GetParser<T>()
		{
			Type targetType = typeof(T);
			DBC.Assert(mParserDelegates.ContainsKey(targetType),
					   String.Format("No parser for type {0}", targetType.FullName));
			return mParserDelegates[targetType];
		}

		/// <summary>
		/// Return the noted app setting as type T; throw an exception if absent from configuration
		/// </summary>
		/// <param name="pKey"></param>
		/// <returns></returns>
		public static T Retrieve<T>(string pKey)
		{
			return Retrieve<T>(pKey, Required.Yes);
		}

		/// <summary>
		/// Return the noted app setting as type T; throw an exception if absent from configuration; 
		/// plus, attempt a guarded parse possibly
		/// </summary>
		/// <param name="pKey"></param>
		/// <returns></returns>
		public static T Retrieve<T>(string pKey, GuardedParse pGuarded)
		{
			return Retrieve<T>(pKey, Required.Yes, pGuarded);
		}

		/// <summary>
		/// Return the noted app setting as type T; substitute the default value 
		/// if absent from configuration
		/// </summary>
		/// <param name="pKey"></param>
		/// <returns></returns>
		public static T Retrieve<T>(string pKey, T pDefault)
		{
			return Retrieve(pKey, Required.No, pDefault, mGuidedParseDefault);
		}

		/// <summary>
		/// Return the noted app setting as type T throwing or suppressing
		/// an exception based on the 'Required' setting
		/// </summary>
		/// <param name="pKey"></param>
		/// <returns></returns>
		public static T Retrieve<T>(string pKey, Required pMandatory)
		{
			return Retrieve(pKey, pMandatory, default(T), mGuidedParseDefault);
		}

		/// <summary>
		/// Return the noted app setting as type T throwing or suppressing
		/// an exception based on the 'Required' setting
		/// </summary>
		/// <param name="pKey"></param>
		/// <returns></returns>
		public static T Retrieve<T>(string pKey, Required pMandatory, GuardedParse pGuarded)
		{
			return Retrieve(pKey, pMandatory, default(T), pGuarded);
		}

		/// <summary>
		/// Return the noted app setting as type T throwing or suppressing
		/// an exception based on the 'Required' setting, using the default if absent
		/// </summary>
		/// <param name="pKey"></param>
		/// <returns></returns>
		private static T Retrieve<T>(string pKey, Required pMandatory, T pDefault, GuardedParse pGuarded)
		{
			string val = SanityCheck(pKey, pMandatory);
			return val == null ? pDefault : (T)GetParser<T>()(pKey, val, pGuarded);
		}

		#region specific type parsers
		// TODO: Could consider using the Converter<S,T>.CreateDelegate(...) here or TypeConverter sub classes

		private static object StringParser(string pKey, string pValue, GuardedParse pGuarded)
		{
			return pValue;
		}

		private static object BooleanParser(string pKey, string pValue, GuardedParse pGuarded)
		{
			bool ret = false;
			if (pGuarded == GuardedParse.No)
				ret = bool.Parse(pValue);
			else
				DBC.Assert(bool.TryParse(pValue, out ret), GuardedParseMessage("bool", pValue, pKey));
			return ret;
		}

		private static object IntegerParser(string pKey, string pValue, GuardedParse pGuarded)
		{
			int ret = 0;
			if (pGuarded == GuardedParse.No)
				ret = int.Parse(pValue);
			else
				DBC.Assert(int.TryParse(pValue, out ret), GuardedParseMessage("int", pValue, pKey));
			return ret;
		}

		private static object UnsignedIntegerParser(string pKey, string pValue, GuardedParse pGuarded)
		{
			uint ret = 0;
			if (pGuarded == GuardedParse.No)
				ret = uint.Parse(pValue);
			else
				DBC.Assert(uint.TryParse(pValue, out ret), GuardedParseMessage("uint", pValue, pKey));
			return ret;
		}


		private static string GuardedParseMessage(string pType, string pValue, string pKey)
		{
			return String.Format("Cannot parse as type \"{0}\" the value \"{1}\" for configuration key \"{2}\"", pType, pValue, pKey);
		}

		#endregion
	}
}

    #endregion
